Skip to main content

第 10 章:在服務器上搭建 Git

現在我們將討論如何在你自己的服務器上搭建 Git 服務來運行這些協議。

Note::: 這里我們將要演示在 Linux 服務器上進行一次基本且簡化的安裝所需的命令與步驟,當然在 macOS 或 Windows 服務器上同樣可以運行這些服務。 事實上,在你的計算機基礎架構中建立一個生產環境服務器,將不可避免的使用到不同的安全措施與操作系統工具。但是,希望你能從本節中獲得一些必要的知識。

在開始架設 Git 服務器前,需要把現有倉庫導出為裸倉庫——即一個不包含當前工作目錄的倉庫。 這通常是很簡單的。 為了通過克隆你的倉庫來創建一個新的裸倉庫,你需要在克隆命令後加上 --bare 選項。 按照慣例,裸倉庫的目錄名以 .git 結尾,就像這樣:

git clone --bare my_project my_project.git
Cloning into bare repository 'my_project.git'...
done.

現在,你的 my_project.git 目錄中應該有 Git 目錄的副本了。

整體上效果大致相當於

cp -Rf my_project/.git my_project.git

雖然在配置文件中有若幹不同,但是對於你的目的來說,這兩種方式都是一樣的。 它只取出 Git 倉庫自身,不要工作目錄,然後特別為它單獨創建一個目錄。

把裸倉庫放到服務器上

既然你有了裸倉庫的副本,剩下要做的就是把裸倉庫放到服務器上並設置你的協議。 假設一個域名為 git.example.com 的服務器已經架設好,並可以通過 SSH 連接, 你想把所有的 Git 倉庫放在 /srv/git 目錄下。 假設服務器上存在 /srv/git/ 目錄,你可以通過以下命令覆制你的裸倉庫來創建一個新倉庫:

scp -r my_project.git [user@git.example.com](mailto:user@git.example.com):/srv/git

此時,其他可通過 SSH 讀取此服務器上 /srv/git 目錄的用戶,可運行以下命令來克隆你的倉庫。

git clone [user@git.example.com](mailto:user@git.example.com):/srv/git/my_project.git

如果一個用戶,通過使用 SSH 連接到一個服務器,並且其對 /srv/git/my_project.git 目錄擁有可寫權限,那麽他將自動擁有推送權限。

如果到該項目目錄中運行 git init 命令,並加上 --shared 選項, 那麽 Git 會自動修改該倉庫目錄的組權限為可寫。 注意,運行此命令的工程中不會摧毀任何提交、引用等內容。

ssh [user@git.example.com](mailto:user@git.example.com)
cd /srv/git/my_project.git
git init --bare --shared

由此可見,根據現有的 Git 倉庫創建一個裸倉庫,然後把它放上你和協作者都有 SSH 訪問權的服務器是多麽容易。 現在你們已經準備好在同一項目上展開合作了。

值得注意的是,這的確是架設一個幾個人擁有連接權的 Git 服務的全部—— 只要在服務器上加入可以用 SSH 登錄的帳號,然後把裸倉庫放在大家都有讀寫權限的地方。 你已經準備好了一切,無需更多。

下面的幾節中,你會了解如何擴展到更覆雜的設定。 這些內容包含如何避免為每一個用戶建立一個賬戶,給倉庫添加公共讀取權限,架設網頁界面等等。 然而,請記住這一點,如果只是和幾個人在一個私有項目上合作的話,僅僅 是一個 SSH 服務器和裸倉庫就足夠了。

小型安裝

如果設備較少或者你只想在小型開發團隊里嘗試 Git ,那麽一切都很簡單。 架設 Git 服務最覆雜的地方在於用戶管理。 如果需要倉庫對特定的用戶可讀,而給另一部分用戶讀寫權限,那麽訪問和許可安排就會比較困難。

SSH 連接

如果你有一台所有開發者都可以用 SSH 連接的服務器,架設你的第一個倉庫就十分簡單了, 因為你幾乎什麽都不用做(正如我們上一節所說的)。 如果你想在你的倉庫上設置更覆雜的訪問控制權限,只要使用服務器操作系統的普通的文件系統權限就行了。

如果需要團隊里的每個人都對倉庫有寫權限,又不能給每個人在服務器上建立賬戶,那麽提供 SSH 連接就是唯一的選擇了。 我們假設用來共享倉庫的服務器已經安裝了 SSH 服務,而且你通過它訪問服務器。

有幾個方法可以使你給團隊每個成員提供訪問權。 第一個就是給團隊里的每個人創建賬號,這種方法很直接但也很麻煩。 或許你不會想要為每個人運行一次 adduser(或者 useradd)並且設置臨時密碼。

第二個辦法是在主機上建立一個 'git' 賬戶,讓每個需要寫權限的人發送一個 SSH 公鑰, 然後將其加入 git 賬戶的 ~/.ssh/authorized_keys 文件。 這樣一來,所有人都將通過 'git' 賬戶訪問主機。 這一點也不會影響提交的數據——訪問主機用的身份不會影響提交對象的提交者信息。

另一個辦法是讓 SSH 服務器通過某個 LDAP 服務,或者其他已經設定好的集中授權機制,來進行授權。 只要每個用戶可以獲得主機的 shell 訪問權限,任何 SSH 授權機制你都可視為是有效的。

服務器上的 Git - 生成 SSH 公鑰

生成 SSH 公鑰

如前所述,許多 Git 服務器都使用 SSH 公鑰進行認證。 為了向 Git 服務器提供 SSH 公鑰,如果某系統用戶尚未擁有密鑰,必須事先為其生成一份。 這個過程在所有操作系統上都是相似的。 首先,你需要確認自己是否已經擁有密鑰。 默認情況下,用戶的 SSH 密鑰存儲在其 ~/.ssh 目錄下。 進入該目錄並列出其中內容,你便可以快速確認自己是否已擁有密鑰:

cd ~/.ssh
ls
authorized_keys2 id_dsa known_hosts
config id_dsa.pub

我們需要尋找一對以 id_dsa 或 id_rsa 命名的文件,其中一個帶有 .pub 擴展名。 .pub 文件是你的公鑰,另一個則是與之對應的私鑰。 如果找不到這樣的文件(或者根本沒有 .ssh 目錄),你可以通過運行 ssh-keygen 程序來創建它們。 在 Linux/macOS 系統中,ssh-keygen 隨 SSH 軟件包提供;在 Windows 上,該程序包含於 MSysGit 軟件包中。

$ ssh-keygen -o
Generating public/private rsa key pair.
Enter file in which to save the key (/home/schacon/.ssh/id_rsa):
Created directory '/home/schacon/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/schacon/.ssh/id_rsa.
Your public key has been saved in /home/schacon/.ssh/id_rsa.pub.
The key fingerprint is:
d0:82:24:8e:d7:f1:bb:9b:33:53:96:93:49:da:9b:e3 schacon@mylaptop.local

首先 ssh-keygen 會確認密鑰的存儲位置(默認是 .ssh/id_rsa),然後它會要求你輸入兩次密鑰口令。 如果你不想在使用密鑰時輸入口令,將其留空即可。 然而,如果你使用了密碼,那麽請確保添加了 -o 選項,它會以比默認格式更能抗暴力破解的格式保存私鑰。 你也可以用 ssh-agent 工具來避免每次都要輸入密碼。

現在,進行了上述操作的用戶需要將各自的公鑰發送給任意一個 Git 服務器管理員 (假設服務器正在使用基於公鑰的 SSH 驗證設置)。 他們所要做的就是覆制各自的 .pub 文件內容,並將其通過郵件發送。 公鑰看起來是這樣的:

cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx
NrRFi9wrf+M7Q== schacon@mylaptop.local

關於在多種操作系統中生成 SSH 密鑰的更深入教程,請參閱 GitHub 的 SSH 密鑰指南 https://docs.github.com/cn/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent。

配置服務器

我們來看看如何配置服務器端的 SSH 訪問。 本例中,我們將使用 authorized_keys 方法來對用戶進行認證。 同時我們假設你使用的操作系統是標準的 Linux 發行版,比如 Ubuntu。 首先,創建一個操作系統用戶 git,並為其建立一個 .ssh 目錄。

Note::: 以下操作可通過 ssh-copy-id 命令自動完成,這樣就不必手動覆制並安裝公鑰了。

首先,創建一個操作系統用戶 git,並為其建立一個 .ssh 目錄。

sudo adduser git
su git
cd
mkdir .ssh && chmod 700 .ssh
touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys

接著,我們需要為系統用戶 git 的 authorized_keys 文件添加一些開發者 SSH 公鑰。 假設我們已經獲得了若幹受信任的公鑰,並將它們保存在臨時文件中。 與前文類似,這些公鑰看起來是這樣的:

cat /tmp/id_rsa.john.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4L
ojG6rs6hPB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+4k
Yjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9Ez
Sdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myiv
O7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPq
dAv8JggJICUvax2T9va5 gsg-keypair

將這些公鑰加入系統用戶 git 的 .ssh 目錄下 authorized_keys 文件的末尾:

cat /tmp/id_rsa.john.pub >> ~/.ssh/authorized_keys
cat /tmp/id_rsa.josie.pub >> ~/.ssh/authorized_keys
cat /tmp/id_rsa.jessica.pub >> ~/.ssh/authorized_keys

現在我們來為開發者新建一個空倉庫。可以借助帶 --bare 選項的 git init 命令來做到這一點,該命令在初始化倉庫時不會創建工作目錄:

cd /srv/git
mkdir project.git
cd project.git
git init --bare
Initialized empty Git repository in /srv/git/project.git/

接著,John、Josie 或者 Jessica 中的任意一人可以將他們項目的最初版本推送到這個倉庫中, 他只需將此倉庫設置為項目的遠程倉庫並向其推送分支。 請注意,每添加一個新項目,都需要有人登錄服務器取得 shell,並創建一個裸倉庫。 我們假定這個設置了 git 用戶和 Git 倉庫的服務器使用 gitserver 作為主機名。 同時,假設該服務器運行在內網,並且你已在 DNS 配置中將 gitserver 指向此服務器。 那麼我們可以運行如下命令(假定 myproject 是已有項目且其中已包含文件):

on John's computer

cd myproject
git init
git add .
git commit -m 'initial commit'
git remote add origin git@gitserver:/srv/git/project.git
git push origin master

此時,其他開發者可以克隆此倉庫,並推回各自的改動,步驟很簡單:

git clone git@gitserver:/srv/git/project.git
cd project
vim README
git commit -am 'fix for the README file'
git push origin master

通過這種方法,你可以快速搭建一個具有讀寫權限、面向多個開發者的 Git 服務器。

需要注意的是,目前所有(獲得授權的)開發者用戶都能以系統用戶 git 的身份登錄服務器從而獲得一個普通 shell。 如果你想對此加以限制,則需要修改 /etc/passwd 文件中(git 用戶所對應)的 shell 值。

借助一個名為 git-shell 的受限 shell 工具,你可以方便地將用戶 git 的活動限制在與 Git 相關的範圍內。 該工具隨 Git 軟件包一同提供。如果將 git-shell 設置為用戶 git 的登錄 shell(login shell), 那麼該用戶便不能獲得此服務器的普通 shell 訪問權限。 若要使用 git-shell,需要用它替換掉 bash 或 csh,使其成為該用戶的登錄 shell。 為進行上述操作,首先你必須確保 git-shell 的完整路徑名已存在於 /etc/shells 文件中:

cat /etc/shells   # see if git-shell is already in there. If not...
which git-shell # make sure git-shell is installed on your system.
sudo -e /etc/shells # and add the path to git-shell from last command

現在你可以使用 chsh <username> -s <shell> 命令修改任一系統用戶的 shell:

sudo chsh git -s $(which git-shell)

這樣,用戶 git 就只能利用 SSH 連接對 Git 倉庫進行推送和拉取操作,而不能登錄機器並取得普通 shell。 如果試圖登錄,你會發現嘗試被拒絕,像這樣:

ssh git@gitserver
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.
Connection to gitserver closed.

此時,用戶仍可通過 SSH 端口轉發來訪問任何可達的 git 服務器。 如果你想要避免它,可編輯 authorized_keys 文件並在所有想要限制的公鑰之前添加以下選項:

no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty

其結果如下:

cat ~/.ssh/authorized_keys
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4LojG6rs6h
PB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+4kYjh6541N
YsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9EzSdfd8AcC
IicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myivO7TCUSBd
LQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPqdAv8JggJ
ICUvax2T9va5 gsg-keypair

no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQDEwENNMomTboYI+LJieaAY16qiXiH3wuvENhBG...

現在,網絡相關的 Git 命令依然能夠正常工作,但是開發者用戶已經無法得到一個普通 shell 了。 正如輸出信息所提示的,你也可以在 git 用戶的主目錄下建立一個目錄,來對 git-shell 命令進行一定程度的自定義。 比如,你可以限制掉某些本應被服務器接受的 Git 命令,或者對剛才的 SSH 拒絕登錄信息進行自定義,這樣,當有開發者用戶以類似方式嘗試登錄時,便會看到你的信息。 要了解更多有關自定義 shell 的信息,請運行 git help shell。

Git 守護進程

接下來我們將通過 “Git” 協議建立一個基於守護進程的倉庫。 對於快速且無需授權的 Git 數據訪問,這是一個理想之選。 請注意,因為其不包含授權服務,任何通過該協議管理的內容將在其網絡上公開。

如果運行在防火墻之外的服務器上,它應該只對那些公開的只讀項目服務。 如果運行在防火墻之內的服務器上,它可用於支撐大量參與人員或自動系統 (用於持續集成或編譯的主機)只讀訪問的項目,這樣可以省去逐一配置 SSH 公鑰的麻煩。

無論何時,該 Git 協議都是相對容易設定的。 通常,你只需要以守護進程的形式運行該命令:

git daemon --reuseaddr --base-path=/srv/git/ /srv/git/

--reuseaddr 選項允許服務器在無需等待舊連接超時的情況下重啟,而 --base-path 選項允許用戶在未完全指定路徑的條件下克隆項目, 結尾的路徑將告訴 Git 守護進程從何處尋找倉庫來導出。 如果有防火墻正在運行,你需要開放端口 9418 的通信權限。

你可以通過許多方式將該進程以守護進程的方式運行,這主要取決於你所使用的操作系統。

由於在現代的 Linux 發行版中,systemd 是最常見的初始化系統,因此你可以用它來達到此目的。 只要在 /etc/systemd/system/git-daemon.service 中放一個文件即可,其內容如下:

[Unit]
Description=Start Git Daemon

[Service]
ExecStart=/usr/bin/git daemon --reuseaddr --base-path=/srv/git/ /srv/git/

Restart=always
RestartSec=500ms

StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=git-daemon

User=git
Group=git

[Install]
WantedBy=multi-user.target

你可能會注意這里以 git 啟動的 Git 駐留程序同時使用了 Group 和 User 權限。 按需修改它並確保提供的用戶在此系統上。此外,請確保 Git 二進制文件位於 /usr/bin/git,必要時可修改此路徑。

最後,你需要運行 systemctl enable git-daemon 以讓它在系統啟動時自動運行, 這樣也能讓它通過 systemctl start git-daemon 啟動,通過 systemctl stop git-daemon 停止。

在其他系統中,你可以使用 sysvinit 系統中的 xinetd 腳本,或者另外的方式來實現——只要你能夠將其命令守護進程化並實現監控。

接下來,你需要告訴 Git 哪些倉庫允許基於服務器的無授權訪問。 你可以在每個倉庫下創建一個名為 git-daemon-export-ok 的文件來實現。

cd /path/to/project.git
touch git-daemon-export-ok該文件將允許 Git 提供無需授權的項目訪問服務。

Smart HTTP

我們一般通過 SSH 進行授權訪問,通過 git:// 進行無授權訪問,但是還有一種協議可以同時實現以上兩種方式的訪問。 設置 Smart HTTP 一般只需要在服務器上啟用一個 Git 自帶的名為 git-http-backend 的 CGI 腳本。 該 CGI 腳本將會讀取由 git fetch 或 git push 命令向 HTTP URL 發送的請求路徑和頭部信息, 來判斷該客戶端是否支持 HTTP 通信(不低於 1.6.6 版本的客戶端支持此特性)。 如果 CGI 發現該客戶端支持智能(Smart)模式,它將會以智能模式與它進行通信, 否則它將會回落到啞(Dumb)模式下(因此它可以對某些老的客戶端實現向下兼容)。

在完成以上簡單的安裝步驟後, 我們將用 Apache 來作為 CGI 服務器。 如果你沒有安裝 Apache,你可以在 Linux 環境下執行如下或類似的命令來安裝:

sudo apt-get install apache2 apache2-utils
a2enmod cgi alias env

該操作將會啟用 mod_cgi, mod_alias 和 mod_env 等 Apache 模塊, 這些模塊都是使該功能正常工作所必須的。

你還需要將 /srv/git 的 Unix 用戶組設置為 www-data,這樣 Web 服務器才能讀寫該倉庫, 因為運行 CGI 腳本的 Apache 實例默認會以該用戶的權限運行:

chgrp -R www-data /srv/git

接下來我們要向 Apache 配置文件添加一些內容,來讓 git-http-backend 作為 Web 服務器對 /git 路徑請求的處理器。

SetEnv GIT_PROJECT_ROOT /srv/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/

如果留空 GIT_HTTP_EXPORT_ALL 這個環境變量,Git 將只對無授權客戶端提供帶 git-daemon-export-ok 文件的版本庫,就像 Git 守護進程一樣。

最後,如果想讓 Apache 允許 git-http-backend 請求並實現寫入操作的授權驗證,使用如下授權屏蔽配置即可:

<Files "git-http-backend">
AuthType Basic
AuthName "Git Access"
AuthUserFile /srv/git/.htpasswd
Require expr !(%{QUERY_STRING} -strmatch '*service=git-receive-pack*' || %{REQUEST_URI} =~ m#/git-receive-pack$#)
Require valid-user
</Files>

這需要你創建一個包含所有合法用戶密碼的 .htpasswd 文件。 以下是一個添加 “schacon” 用戶到此文件的例子:

htpasswd -c /srv/git/.htpasswd schacon

你可以通過許多方式添加 Apache 授權用戶,選擇使用其中一種方式即可。 以上僅僅只是我們可以找到的最簡單的一個例子。 如果願意的話,你也可以通過 SSL 運行它,以保證所有數據是在加密狀態下進行傳輸的。

我們不想深入去講解 Apache 配置文件,因為你可能會使用不同的 Web 服務器,或者可能有不同的授權需求。 它的主要原理是使用一個 Git 附帶的,名為 git-http-backend 的 CGI。它被引用來處理協商通過 HTTP 發送和接收的數據。 它本身並不包含任何授權功能,但是授權功能可以在 Web 服務器層引用它時被輕松實現。 你可以在任何所有可以處理 CGI 的 Web 服務器上辦到這點,所以隨便挑一個你最熟悉的 Web 服務器試手吧。